/*
 * socket.cpp
 *
 *  Created on: 2008-7-18
 *      Author: fanliangliang
 */

#include "socket.h"
#include "socket_exception.h"

BEGIN_NAMESPACE_TOXIC

#if defined(PLATFORM_WIN32)
SocketInitializer Socket::__the_fucking_winsock_initializer;
#endif

Socket::Socket(SocketType type)
:_descriptor(0)
,_type(type)
,_state(UNCONNECTED_STATE) {
	if (_type == TCP_SOCKET) {
		_descriptor = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	}
	else if (_type == UDP_SOCKET) {
		_descriptor = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	}
	else {
		// not very possible
		throw SocketException("unknown socket type");
	}

	if (_descriptor < 0) {
		throw SocketException();
	}
}

Socket::Socket(int descriptor, SocketType type, SocketState state)
:_descriptor(descriptor)
,_type(type)
,_state(state) {

}

Socket::~Socket() {
	close();
}

void Socket::close() {
	if (_descriptor != 0) {
		setState(CLOSING_STATE);
		::closesocket(_descriptor);
		_descriptor = 0;
		setState(UNCONNECTED_STATE);
	}
}

void Socket::connect(const IpAddress& address, uint16_t port) {
	connect(SocketAddress(address, port));
}

void Socket::connect(const SocketAddress& address) {
	setState(HOST_LOOKUP_STATE);

	SocketAddress host(address);
	if (!host.isValid()) {
		string addr = host.address().toString();
		if (addr.find_first_not_of("1234567890.") != string::npos) {
			// addr maybe a hostname
			IpAddress ip = IpAddress::resolve(addr);
			if (ip == IpAddress::ANY) {
				throw SocketException("cannot resolve host name: " + addr);
			}
			host.setAddress(ip);
		}
	}

	setState(CONNECTING_STATE);
	if (::connect(_descriptor, host, host.size()) != 0) {
		throw SocketException("cannot connect to host: " + host.toString());
	}
	setState(CONNECTED_STATE);
}

size_t Socket::send(const void* buffer, size_t length) {
	const char* ptr = static_cast<const char*>(buffer);
	size_t nsend = ::send(_descriptor, ptr, length, 0);
	if (nsend == size_t(-1)) {
		throw SocketException();
	}
	return nsend;
}

size_t Socket::sendN(const void* buffer, size_t length) {
	const char* ptr = static_cast<const char*>(buffer);
	size_t nleft = length;

	while (nleft > 0) {
		size_t nsend = ::send(_descriptor, ptr, nleft, 0);
		if (nsend == size_t(-1)) {
			throw SocketException();
		}
		nleft -= nsend;
		ptr += nsend;
	}
	return length;
}

size_t Socket::receive(void* buffer, size_t length) {
	char* ptr = static_cast<char*>(buffer);
	size_t nrecv = ::recv(_descriptor, ptr, length, 0);
	if (nrecv == size_t(-1)) {
		throw SocketException();
	}
	return nrecv;
}

size_t Socket::receiveN(void* buffer, size_t length) {
	char* ptr = static_cast<char*>(buffer);
	size_t nleft = length;

	while (nleft > 0) {
		size_t nrecv = ::recv(_descriptor, ptr, nleft, 0);
		// the connection has been gracefully closed
		if (nrecv == 0) {
			break;
		}
		if (nrecv == size_t(-1)) {
			throw SocketException();
		}
		nleft -= nrecv;
		ptr += nrecv;
	}

	return length - nleft;
}

SocketAddress Socket::localAddress() const {
	socklen_t length = SocketAddress::size();
	sockaddr_in addr;
	memset(&addr, 0, sizeof(sockaddr_in));
	int result = getsockname(_descriptor, reinterpret_cast<sockaddr*>(&addr), &length);
	if (result != 0) {
		return SocketAddress();
	}
	return SocketAddress(addr);
}

IpAddress Socket::localIpAddress() const {
	return localAddress().address();
}

uint16_t Socket::localPort() const {
	return localAddress().port();
}

SocketAddress Socket::peerAddress() const {
	socklen_t length = SocketAddress::size();
	sockaddr_in addr;
	memset(&addr, 0, sizeof(sockaddr_in));
	int result = getpeername(_descriptor, reinterpret_cast<sockaddr*>(&addr), &length);
	if (result != 0) {
		return SocketAddress();
	}
	return SocketAddress(addr);
}

IpAddress Socket::peerIpAddress() const {
	return peerAddress().address();
}

uint16_t Socket::peerPort() const {
	return peerAddress().port();
}

int Socket::descriptor() const {
	return _descriptor;
}

Socket::SocketType Socket::type() const {
	return _type;
}

Socket::SocketState Socket::state() const {
	return _state;
}

bool Socket::isConnected() const {
	return _state == CONNECTED_STATE;
}

void Socket::setState(SocketState state) {
	_state = state;
}

END_NAMESPACE_TOXIC
